using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Runtime.InteropServices;

namespace csammisrun.OscarLib.Utility
{
	/// <summary>
	/// Provides a set of static methods for dispatching incoming and outgoing SNAC data
	/// </summary>
	class SNACFunctions
	{
		/// <summary>
		/// Processes an error notification -- SNAC(XX,01)
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object containing SNAC(XX,01)</param>
		public static void ProcessErrorNotification(DataPacket dp)
		{
			byte[] SNACData = dp.Data;

			int index = 0;
			ushort errorcode = Marshal.ByteArrayToUshort(SNACData, ref index);
			ushort subcode = 0xFFFF;
			
      /*
       * The Shutko documentation says that there should be an error subcode here
       * in TLV 0x0008, but the one time I've seen this (testing chat connections),
       * 0x00040003 came back to me
       */
			if(index != SNACData.Length)
			{
        //TLV tlv = Marshal.GetTLV(SNACData, ref index);
        //index = 0;
        //subcode = Marshal.ByteArrayToUshort(tlv.Data, ref index);
			}

			StringBuilder sb = new StringBuilder();
			sb.AppendFormat(System.Globalization.CultureInfo.CurrentCulture.NumberFormat, "SNAC(0x{0:x4}, 0x0001) received: ", dp.SNAC.FamilyServiceID);
			sb.Append(Definitions.GetServerErrorCode(errorcode));
			if(subcode != 0xFFFF)
			{
				sb.AppendFormat(System.Globalization.CultureInfo.CurrentCulture.NumberFormat, " (subcode 0x{0:x4})", subcode);
			}
			else
			{
				sb.Append(" (no subcode)");
			}

			dp.ParentSession.OnWarning(Definitions.GetServerErrorCode(errorcode));
		}

		#region SNAC enqueuing
		/// <summary>
		/// Enqueues a SNAC for transmission with the appropriate RateClass
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		/// <remarks>
		/// <para>
		/// This needs commenting
		/// </para>
		/// </remarks>
		public static void BuildFLAP(DataPacket dp)
		{
			// Build the FLAP header
			dp.FLAP.DatagramSequenceNumber = 0;	// Gets bound in Connection.cs
			dp.FLAP.Channel = (byte)FLAPChannels.SNACData;

			// Build the size of the data to be sent
			int bytes = 16; // SNAC and FLAP header sizes
			bytes += (dp.Data == null) ? 0 : dp.Data.Length;
			dp.FLAP.DataSize = (ushort)(bytes - 6); // Don't include the FLAP header size

			// Create a buffer and insert the data
			byte[] buffer = new byte[bytes];
			int index = 0;
			Marshal.InsertFLAPHeader(dp.FLAP, buffer, ref index);
			Marshal.InsertSNACHeader(dp.SNAC, buffer, ref index);
			if(dp.Data != null) Marshal.CopyArray(dp.Data, buffer, 0, index);

			dp.Data = buffer;

			SNACFunctions.DispatchToRateClass(dp);
		}

		/// <summary>
		/// Send a data packet to the rate class that will send it
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		public static void DispatchToRateClass(DataPacket dp)
		{
			if(dp.ParentConnection == null)
			{
        dp.ParentConnection = dp.ParentSession.Connections.GetByFamily(
          dp.SNAC.FamilyServiceID);
        if (dp.ParentConnection == null || dp.ParentConnection.ReadyForData == false)
        {
          dp.ParentSession.Connections.AddDelayedPacket(dp.SNAC.FamilyServiceID, dp);
          return;
        }
			}

			int key = (int)(dp.SNAC.FamilyServiceID << 16) | dp.SNAC.FamilySubtypeID;			
			RateClass rc = dp.ParentSession.RateClasses[key];
			if(rc != null)
			{
				rc.Enqueue(dp);
			}
			else
			{
				dp.ParentConnection.SendFLAP(dp.Data);
			}
		}
		#endregion

		#region SNAC processing
		/// <summary>
		/// Dispatch a newly received SNAC to a handler function
		/// </summary>
		/// <param name="dp">A <see cref="DataPacket"/> object</param>
		/// <remarks>
		/// This function is HUGE! NDisplay gave it an atrocious complexity rating, but
		/// it's as straightforward as a giant select() statement can be.
		/// </remarks>
		public static void ProcessSNAC(object obj)
		{
      DataPacket dp = obj as DataPacket;
      Logging.DumpDataPacket(dp);

			switch((SNACFamily)dp.SNAC.FamilyServiceID)
			{
				case SNACFamily.AdministrativeService:
				{
					AdministrativeService sub = (AdministrativeService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case AdministrativeService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case AdministrativeService.AccountConfirmResponse:
							break;
						case AdministrativeService.AccountDeleteResponse:
							break;
						case AdministrativeService.AccountInfoChangeResponse:
							break;
						case AdministrativeService.AccountInfoResponse:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.AdvertisementsService:
				{
					AdvertisementsService sub = (AdvertisementsService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case AdvertisementsService.AdResponse:
							break;
						case AdvertisementsService.ClientServerError:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.AuthorizationRegistrationService:
				{
					AuthorizationRegistrationService sub = (AuthorizationRegistrationService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case AuthorizationRegistrationService.ServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case AuthorizationRegistrationService.MD5AuthkeyResponse:	// 0x0002
							SNAC17.SendAuthorizationRequest(dp);
							break;
						case AuthorizationRegistrationService.LoginResponse:	// 0x0003
							SNAC17.ProcessLoginResponse(dp);
							break;
						case AuthorizationRegistrationService.NewUINResponse:
							break;
						case AuthorizationRegistrationService.SecureIDRequest:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.BroadcastService:
				{
					BroadcastService sub = (BroadcastService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case BroadcastService.ServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case BroadcastService.BroadcastMessageResponse:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.BuddyListManagementService:
				{
					BuddyListManagementService sub = (BuddyListManagementService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case BuddyListManagementService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case BuddyListManagementService.LimitationsParamsResponse:	// 0x0003
							SNAC03.ProcessParametersList(dp);
							break;
						case BuddyListManagementService.NotificationRejected:
							break;
						case BuddyListManagementService.WatcherListResponse:	// 0x0007
							SNAC03.ProcessWatcherList(dp);
							break;
						case BuddyListManagementService.WatcherNotification:
							break;
						case BuddyListManagementService.UserOnlineNotification:	// 0x000B
							SNAC03.ProcessOnlineNotification(dp);
							break;
						case BuddyListManagementService.UserOfflineNotification:	// 0x000C
							SNAC03.ProcessOfflineNotification(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.ChatNavigationService:
				{
					ChatNavigationService sub = (ChatNavigationService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case ChatNavigationService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case ChatNavigationService.InformationResponse: // 0x0009
              SNAC0D.ProcessChatRoomInformation(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.ChatService:
				{
					ChatService sub = (ChatService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case ChatService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
            case ChatService.UsersJoinedNotification: // 0x0003
              SNAC0E.ProcessUsersJoined(dp);
              break;
            case ChatService.UsersLeftNotification: // 0x0004
              SNAC0E.ProcessUsersLeft(dp);
              break;
						case ChatService.MessageToClient: // 0x0006
              SNAC0E.ProcessChatRoomMessage(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.DirectoryUserSearch:
				{
					DirectorySearch sub = (DirectorySearch)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case DirectorySearch.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case DirectorySearch.SearchUserResponse:	// 0x0003
							SNAC0F.ProcessSearchResults(dp);
							break;
						case DirectorySearch.InterestsListResponse:	// 0x0005
							SNAC0F.ProcessInterestList(dp);
							break;
					}
				}
					break;
				case SNACFamily.EmailService:
				{
					switch(dp.SNAC.FamilySubtypeID)
					{
						case 0x0007:
							SNAC18.ProcessEmailInformation(dp);
							break;
					}
				}
					break;
				case SNACFamily.GenericServiceControls:
				{
					GenericServiceControls sub = (GenericServiceControls)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case GenericServiceControls.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case GenericServiceControls.SupportedSNACFamiles:	// 0x0003
							SNAC01.ProcessServicesList(dp);
							break;
						case GenericServiceControls.NewServiceResponse:	// 0x0005
							SNAC01.ProcessNewServiceResponse(dp);
							break;
						case GenericServiceControls.RateLimitResponse:	// 0x0007
							SNAC01.ProcessRateLimitations(dp);
							break;
						case GenericServiceControls.EvilNotification:
							break;
						case GenericServiceControls.MigrationNotice:
							break;
						case GenericServiceControls.NoOp:
							break;
            case GenericServiceControls.RateLimitChangedResponse: // 0x000A
              SNAC01.ProcessRateChange(dp);
              break;
            case GenericServiceControls.PauseRequest: // 0x000B
              SNAC01.ProcessPauseRequest(dp);
              break;
            case GenericServiceControls.ResumeRequest: // 0x000D
              SNAC01.ProcessResumeRequest(dp);
              break;
						case GenericServiceControls.OwnInformationResponse:	// 0x000F
							SNAC01.ProcessOwnInformation(dp);
							break;
            case GenericServiceControls.MessageOfTheDay:	// 0x0013
              SNAC01.ProcessMOTD(dp);
              break;
						case GenericServiceControls.ServerServicesVersionResponse:	// 0x0018
							SNAC01.ProcessVersionsList(dp);
							break;
						case GenericServiceControls.WellKnownURLs:
							break;
						case GenericServiceControls.ClientVerificationRequest:	// 0x001F
							SNAC01.ProcessVerificationRequest(dp);
							break;
            case GenericServiceControls.ClientExtendedStatus: // 0x0021
              SNAC01.ProcessExtendedStatus(dp);
              break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.ICBMService:
				{
					ICBMService sub = (ICBMService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case ICBMService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case ICBMService.ParametersInformationResponse:	// 0x0005
							SNAC04.ProcessParametersList(dp);
							break;
						case ICBMService.GetMessageFromServer:	// 0x0007
							SNAC04.ProcessMessage(dp);
							break;
						case ICBMService.EvilResponse:	// 0x0009
							SNAC04.ProcessWarningAcknowledgement(dp);
							break;
						case ICBMService.MessageNotDelivered:	// 0x000A
							SNAC04.ProcessUndeliverableMessage(dp);
							break;
						case ICBMService.ServerMessageResponse:	// 0x000C
							SNAC04.ProcessServerAcknowledgement(dp);
							break;
						case ICBMService.MiniTypingNotifications:	// 0x0014
							SNAC04.ProcessTypingNotification(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.ICQExtensionsService:
				{
					ICQExtensionsService sub = (ICQExtensionsService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case ICQExtensionsService.ClientServerError:	// 0x0001
							// Needs a specialized handler
							break;
						case ICQExtensionsService.MetaInformationResponse:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.InvitationService:
				{
					InvitationService sub = (InvitationService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case InvitationService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case InvitationService.InvitationResponse:	// 0x0003
							SNAC06.ProcessInvitationConfirmation(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.LocationServices:
				{
					LocationServices sub = (LocationServices)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case LocationServices.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case LocationServices.LimitationsParamsResponse:	// 0x0003
							SNAC02.ProcessParametersList(dp);
							break;
						case LocationServices.UserInformationResponse:	// 0x0006
							SNAC02.ProcessUserInfo(dp);
							break;
						case LocationServices.UpdateDirectoryInfoResponse:	// 0x000A
							SNAC02.ProcessUpdateResult(dp);
							break;
						case LocationServices.SNAC020BReply:
							SNAC02.ProcessSelfLocationReply(dp);	// 0x000C
							break;
						case LocationServices.UpdateInterestsResponse:	// 0x0010
							SNAC02.ProcessUpdateResult(dp);
							break;
						case LocationServices.WatcherReply:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.PopupNoticesService:
				{
					PopupNoticesService sub = (PopupNoticesService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case PopupNoticesService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case PopupNoticesService.DisplayPopupMessage:	// 0x0002
							SNAC08.ProcessPopupMessage(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.PrivacyManagementService:
				{
					PrivacyManagementService sub = (PrivacyManagementService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case PrivacyManagementService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case PrivacyManagementService.ServiceError:
							break;
						case PrivacyManagementService.ServiceParametersResponse:
							SNAC09.ProcessParametersList(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.SSBIService:
				{
					SSBIService sub = (SSBIService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case SSBIService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case SSBIService.ICQBuddyIconResponse:
							break;
            case SSBIService.UploadIconResponse:   // 0x0003
              SNAC10.ProcessUploadIconResponse(dp);
              break;
            case SSBIService.AIMBuddyIconResponse:  // 0x0005
              SNAC10.ProcessIconDownload(dp);
              break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.SSIService:
				{
					SSIService sub = (SSIService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case SSIService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case SSIService.ServiceParametersResponse:	// 0x0003
							SNAC13.ProcessParametersList(dp);
							break;
						case SSIService.ContactListResponse:	// 0x0006
							SNAC13.ProcessBuddyList(dp);
							break;
            case SSIService.SSIEditAddItems:  // 0x0008
              SNAC13.ProcessItemsAdded(dp);
							break;
            case SSIService.SSIEditUpdateGroupHeader:  // 0x0009
              SNAC13.ProcessItemsModified(dp);
							break;
						case SSIService.SSIEditAcknowledge:	// 0x000E
							SNAC13.ProcessSSIUpdateResponse(dp);
							break;
						case SSIService.LocalSSIUpToDate:	// 0x000F
							SNAC13.ProcessSSIUpToDate(dp);
							break;
						case SSIService.YouWereAddedMessage:	// 0x001C
							SNAC13.ProcessAddedMessage(dp);
							break;
            case SSIService.AuthorizationResponse:
              break;
            case SSIService.SSIEditRemoveItem:
              break;
            case SSIService.GrantAuthorizationResponse:
              break;
            case SSIService.SendAuthorizationRequest:
              break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.TranslationService:
				{
					TranslationService sub = (TranslationService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case TranslationService.ClientServerError:
							break;
						case TranslationService.TranslateResponse:
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.UsageStatsService:
				{
					UsageStatsService sub = (UsageStatsService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case UsageStatsService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case UsageStatsService.SetMinimumInterval:	// 0x0002
							SNAC0B.ProcessReportingInterval(dp);
							break;
						case UsageStatsService.UsageReportAck:	// 0x0004
							SNAC0B.ProcessReportAcknowledgement(dp);
							break;
						default:
							break;
					}
				}
					break;
				case SNACFamily.UserLookupService:
				{
					UserLookupService sub = (UserLookupService)dp.SNAC.FamilySubtypeID;
					switch(sub)
					{
						case UserLookupService.ClientServerError:	// 0x0001
							SNACFunctions.ProcessErrorNotification(dp);
							break;
						case UserLookupService.FindUserByEmailResponse:	// 0x0003
							SNAC0A.ProcessSearchResults(dp);
							break;
						default:
							break;
					}
				}
					break;
				default:
					StringBuilder sb = new StringBuilder();
					sb.AppendFormat(
						System.Globalization.CultureInfo.CurrentCulture.NumberFormat, "Unknown SNAC: ({0:x4},{1:x4}), flags = {2:x4}, requestID = {3:x8}",
						dp.SNAC.FamilyServiceID,
						dp.SNAC.FamilySubtypeID,
            dp.SNAC.Flags,
            dp.SNAC.RequestID);
          Logging.DumpFLAP(dp.Data, sb.ToString());
					dp.ParentSession.OnWarning(ServerErrorCode.UnknownSNACFamily);
					break;
			}
		}
		#endregion
	}
}
